package recom

import (
	"database/sql"
	"fmt"
	"os"
	"sync"
)

import (
	_ "github.com/denisenkom/go-mssqldb"
)

import (
	"buyChance"
	dbc "dbConnector"
	"r_log"
	"resource"
	"tp"
	"util"
)

type Recommender struct {
	Bcs           *buyChance.BuyChanceBase
	Tps           *tp.TradingPosition
	Res           *resource.Resource
	Results       map[util.UserID]*RecommenderResultsInOneDay
	ResultLock    sync.RWMutex
	NewResults    map[string]*[]RecommenderResult
	NewRInfos     map[string]*[]RecommenderResultInfo
	NewRLock      sync.Mutex
	LastCalcPoint util.TimeStamp
	Statistic     [util.PERIOD_COUNT]Statistics
}

type Statistics struct {
	CountOfGetStock    int
	CountOfNotGetStock int
}

type RecommenderResultsInOneDay map[util.StkID]util.Period
type RecommenderResult struct {
	UserId       util.UserID
	Period       util.Period
	FlowId       uint64
	Date, Time   util.DateAndTime
	LastDate     util.DateAndTime //最终有效日期
	Batch        int32
	Stkid        util.StkID
	Type         util.ModeType
	MethodStyle  util.MethodStyle //打法风格
	Capital      int64            //总金额
	Price0       int32
	Price1       int32
	Price2       int32
	Price3       int32
	Price4       int32
	Price5       int32
	Capital1st   int64 //首仓限额（如果只有一仓则填0）
	Capital2nd   int64 //2仓限额（如果只有一或两仓则填0）
	Price2nd     int32 //2仓下调点位（万分之）（如果只有一仓则填0）
	Price3rd     int32 //3仓下调点位（万分之）（如果只有一或两仓则填0）
	RId          util.ResultID
	GId          util.ResultID
	MarketSector util.MarketCode
	TimeStamp    util.TimeStamp
}

type RecommenderResultInfo struct {
	UserId    util.UserID
	Period    util.Period
	FlowId    uint64
	TimeStamp util.TimeStamp
	Info      ResultInfo
	Date      util.DateAndTime
	Batch     int32
}
type ResultInfo uint32

const (
	RI_HaveRecomm ResultInfo = 1
	RI_NORecomm   ResultInfo = 2
	RI_NOPosition ResultInfo = 3
)

func (r *Recommender) Init() error {
	r.Results = make(map[util.UserID]*RecommenderResultsInOneDay)
	r.NewResults = make(map[string]*[]RecommenderResult)
	r.NewRInfos = make(map[string]*[]RecommenderResultInfo)
	r.initLastCalcPoint()
	err := r.init()
	if err != nil {
		return err
	}
	return nil
}

func (r *Recommender) initLastCalcPoint() {
	f, err := os.Open("../../savefile/LastCalcPoint")
	if err != nil {
		r.LastCalcPoint = 0
		return
	}
	defer f.Close()
	b := make([]byte, 8)
	f.Read(b)
	r.LastCalcPoint = util.TimeStamp(util.Byte2Uint64(b))
}

//交易日切换时重置result数据
func (r *Recommender) ResetResult() {
	r.Results = make(map[util.UserID]*RecommenderResultsInOneDay)
}

func (r *Recommender) init() error {
	dbc.ServerLock.RLock()
	sl := make([]dbc.ServerInfo, 0, len(dbc.Servers.Account)) //数据库地址列表
	for _, si := range dbc.Servers.Account {
		sl = append(sl, *si)
	}
	dbc.ServerLock.RUnlock()

	r.NewRLock.Lock()
	for _, s := range sl {
		nr := make([]RecommenderResult, 0)
		r.NewResults[s.DataBase] = &nr
	}
	r.NewRLock.Unlock()

	tradedate := util.Today()
	todayIsHoliday, err := r.Res.TodayIsHoliday()
	if err != nil {
		return fmt.Errorf("r.Res.TodayIsHoliday(): %v", err)
	}
	if todayIsHoliday || util.GetTime() > util.DateAndTime(150000) {
		tradedate, err = r.Res.GetNextMarketDay(tradedate)
		if err != nil {
			return fmt.Errorf("r.Res.GetNextMarketDay(%v): %v", tradedate, err)
		}
	}

	//获取信息
	errorlist := make([]error, 0)
	var errorlock sync.Mutex
	var wg sync.WaitGroup
	for _, s := range sl {
		wg.Add(1)
		go func(server dbc.ServerInfo) {
			defer wg.Done()
			db := server.DB
			/*			db, err := server.InitSqlServer()
						if err != nil {
							errorlock.Lock()
							errorlist = append(errorlist, fmt.Errorf("initSqlServer(%v): %v", server, err))
							errorlock.Unlock()
							return
						}*/

			err = r.loadResults(db, tradedate)
			if err != nil {
				db.Close()
				errorlock.Lock()
				errorlist = append(errorlist, fmt.Errorf("r.loadResults(%v,%v): %v", server, tradedate, err))
				errorlock.Unlock()
				return
			}
			//		db.Close()
		}(s)
	}
	wg.Wait()

	if len(errorlist) != 0 {
		return fmt.Errorf("Get err when load recom results! %v", errorlist)
	}
	return nil
}

func (r *Recommender) loadResults(db *sql.DB, tradedate util.DateAndTime) error {
	rows, err := db.Query("P_RecommenderServer_LoadResultByDate ?", tradedate)
	if err != nil {
		return fmt.Errorf("db.Query(): %v", err)
	}
	uid := new(util.UserID)
	klineType := new(util.Period)
	stkid := new(util.StkID)

	lastUserid := util.UserID(0)
	userCount := 0
	var rrsp *RecommenderResultsInOneDay
	for rows.Next() {
		if err = rows.Scan(uid, klineType, stkid); err != nil {
			r_log.Logger.Warn("rows.Scan(): %v", err)
			continue
		}
		if userCount == 0 || lastUserid != *uid {
			userCount++
			lastUserid = *uid
			rrsp = new(RecommenderResultsInOneDay)
			rrs := make(RecommenderResultsInOneDay)
			rrsp = &rrs

			r.ResultLock.Lock()
			r.Results[*uid] = rrsp
			r.ResultLock.Unlock()
		}
		(*rrsp)[*stkid] = *klineType
	}
	if err = rows.Err(); err != nil {
		r_log.Logger.Warn("rows.Err(): %v", err)
	}
	return nil
}

//sort
func pcsSort(pcs *[]tp.PointChange) {
	pcsQuickSort(pcs, 0, len(*pcs)-1)
}

func pcsQuickSort(pcs *[]tp.PointChange, start, end int) {
	if start < end {
		i, j := start, end
		key := (*pcs)[(start+end)/2].State.Priority
		keyUserid := (*pcs)[(start+end)/2].Acc.UserId
		for i <= j {
			for (*pcs)[i].State.Priority > key || ((*pcs)[i].State.Priority == key && (*pcs)[i].Acc.UserId < keyUserid) {
				i++
			}
			for (*pcs)[j].State.Priority < key || ((*pcs)[j].State.Priority == key && (*pcs)[j].Acc.UserId > keyUserid) {
				j--
			}
			if i <= j {
				(*pcs)[i], (*pcs)[j] = (*pcs)[j], (*pcs)[i]
				i++
				j--
			}
		}

		if start < j {
			pcsQuickSort(pcs, start, j)
		}
		if end > i {
			pcsQuickSort(pcs, i, end)
		}
	}
}
